package org.doubango.networkdemo

import android.content.Context
import android.net.ConnectivityManager
import android.net.LinkProperties
import android.net.Network
import android.net.NetworkCapabilities
import android.os.Build
import android.os.Handler
import android.os.Looper
import org.doubango.networkdemo.NetUtil.NetInterface
import timber.log.Timber
import java.net.Inet4Address
import java.net.Inet6Address

class NetworkCallbackImpl(val hasNetwork: (NetworkCapabilities) -> Unit, val noNetwork: () -> Unit, val hasIp: (String) -> Unit) :  ConnectivityManager.NetworkCallback() {

    private val handler = Handler(Looper.getMainLooper())

    private val connectivityManager = App.app.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

    /**
     * 在框架连接并声明一个新的网络可供使用时调用。当系统检测到某个 新的网络可用 时回调，比如 Wi-Fi 连接成功、蜂窝网络可用、有线网插入等，表示应用可以开始在这个 Network 上进行数据通信。
     * 对于注册了 #registerNetworkCallback 的回调函数，多个网络可能同时可用，每个网络出现时都会调用onAvailable。
     * 对于注册了 #requestNetwork 和 #registerDefaultNetworkCallback 的回调函数，这意味着作为参数传递的网络是该请求的最佳网络，现在被该回调函数跟踪，
     * 此回调将不再接收先前可能传递给此方法的关于其他网络的方法调用。以前最好的网络可能已经断开了，或者它可能仍然存在，而最新最好的网络可能只是更好。
     * 从Android 8.0开始，接下来总是立即调用onCapabilitiesChanged，然后调用onLinkPropertiesChanged、然后调用onBlockedStatusChanged。
     * 不要在这个回调函数中调用 getNetworkCapabilities(Network) 或 getLinkProperties(Network) 或其他同步ConnectivityManager方法，
     * 因为这容易出现竞争条件（不能保证这些方法返回的对象是当前的）。相反，
     * 等待对 onCapabilitiesChanged(Network,NetworkCapabilities) 和 onLinkPropertiesChanged(Network,LinkProperties) 的调用，它们的参数保证相对于其他回调函数是有序的。
     * @param network 满足网络的 Network 。该值不能为 null 。
     */
    override fun onAvailable(network: Network) {
        printNetwork("onAvailable", network)
    }

    /**
     * 如果回调是用 requestNetwork 方法注册的，那么如果在 requestNetwork(NetworkCallback,int) 调用中指定的超时时间内没有找到网络，
     * 或者请求的网络请求无法满足（无论是否指定了超时时间），就会调用该回调函数。
     * 当应用请求的网络（比如 requestNetwork()）无法满足条件 或 最终没有建立 时触发。比如请求一个具有 NET_CAPABILITY_INTERNET 的网络，但系统没找到符合条件的网络。
     * 如果在预留网络时注册了回调函数，则此方法表示已删除预留。当系统无法满足预留请求时，或者预留网络连接后，可以调用它。
     * 当这个回调被调用时，关联的 NetworkRequest 将已经被删除和释放，就像 unregisterNetworkCallback(android.net.ConnectivityManager.NetworkCallback) 被调用了一样。
     */
    override fun onUnavailable() {
        Timber.i("onUnavailable")
    }

    /**
     * 当对指定网络的访问被阻塞或未阻塞时调用。
     * 例如：
     *      - 后台网络限制（电量优化/数据节省模式）。
     *      - 家长控制阻止应用访问某个网络。
     * 不要在这个回调函数中调用 getNetworkCapabilities(Network) 或 getLinkProperties(Network) 或其他同步ConnectivityManager方法，
     * 因为这很容易出现竞争条件：在回调函数中调用这些方法可能会返回过时的对象，甚至是null对象。
     * @param network 阻塞状态发生改变的 Network 。该值不能为 null 。
     * @param blocked 网络的阻塞状态
     */
    override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
        Timber.i("onBlockedStatusChanged,network=$network,blocked=$blocked")
    }

    /**
     * 当与此请求对应的网络改变了功能，但仍然满足所请求的条件时调用。
     * 从 Android 8.0开始，这个方法保证会在 onAvailable 之后立即被调用。
     * 不要在这个回调中调用 getLinkProperties(Network) 或其他同步ConnectivityManager方法，
     * 因为这容易出现竞争条件：在回调中调用这些方法可能会返回过时的对象，甚至是null对象。
     *
     * 当 网络的能力发生变化 时回调。
     * 例如：
     *      - Wi-Fi 连接上了但还没联网 → NET_CAPABILITY_INTERNET 变化
     *      - 蜂窝网络切换到 5G，能力变化
     *      - VPN 添加/移除某些功能
     * 常见能力：
     *      - NET_CAPABILITY_INTERNET（是否能上网）
     *      - NET_CAPABILITY_VALIDATED（是否能正常访问互联网，经过系统验证）
     *      - NET_CAPABILITY_NOT_METERED（是否为非计费网络，比如 Wi-Fi）
     * @param network 能力发生变化的 Network 。该值不能为 null 。
     * @param networkCapabilities 此网络的新 NetworkCapabilities 。该值不能为 null 。
     */
    override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
        Timber.i("onCapabilitiesChanged,network=$network,networkCapabilities=$networkCapabilities")
        // 为了预防短时间多次调用的情况，这里延迟2秒再调用onAvailable
        handler.removeCallbacksAndMessages(null)
        handler.postDelayed({ hasNetwork(networkCapabilities) }, 2000)
    }

    /**
     * 当该请求对应的网络改变了LinkProperties时调用。
     * 从 Android 8.0开始，这个方法保证会在 onAvailable 之后立即被调用。
     * 不要在这个回调中调用 getNetworkCapabilities(Network) 或其他同步ConnectivityManager方法，
     * 因为这容易出现竞争条件：在回调中调用这些方法可能会返回过时的对象，甚至是null对象。
     * @param network link 属性发生变化的 Network 。该值不能为 null 。
     *
     * 当 网络的链路属性发生变化 时回调。
     * 例如：
     *      - IP 地址变化
     *      - DNS 服务器变化
     *      - 路由表变化
     * LinkProperties 包含 IP、网关、DNS 等信息。
     * @param linkProperties 此网络的新 LinkProperties 。该值不能为 null 。
     */
    override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) {
        Timber.i("onLinkPropertiesChanged,network=$network,linkProperties=$linkProperties")
        linkProperties.linkAddresses.firstOrNull { it.address is Inet4Address && it.address.isLoopbackAddress.not() }?.address?.hostAddress?.let { ip ->
            hasIp(ip)
        }
    }

    /**
     * 当网络即将丢失时调用，这通常是因为该网络上已经没有未完成的请求。系统预计 该网络即将丢失 时回调。
     * 此方法可能与新替换网络的 NetworkCallback.onAvailable调用配对出现，以实现优雅的切换。
     * 这个方法不能保证在调用 NetworkCallback.onLost 之前被调用，例如在网络突然断开的情况下。
     * 关键场景：onLosing通常是一个“预告”，预示着系统检测到当前网络即将不可用（例如，Wi-Fi 信号变弱且没有其他应用在使用此网络），但尚未完全断开。
     * 不保证调用：这是一个重要提示。并非所有网络断开都会经历 onLosing阶段。如果网络连接是突然中断的（例如直接拔掉网线），
     * 那么 onLost可能会被直接调用，而不会先调用 onLosing。因此，你的应用逻辑不能完全依赖于此方法的调用。
     * 不要在此回调中调用 getNetworkCapabilities(Network) 或 getLinkProperties(Network) 或其他同步ConnectivityManager方法，
     * 因为这容易出现竞争条件；在回调函数中调用这些方法可能会返回过时的对象，甚至null对象。
     * @param network 即将丢失的 Network 。该值不能为 null 。
     * @param maxMsToLive 以毫秒为单位的系统为了优雅切换而保持网络连接的时间；请注意，网络仍然可能在任何时候遭受硬损失。maxMsToLive 给出了一个 预计的剩余可用时间 (毫秒)，不是保证值。
     */
    override fun onLosing(network: Network, maxMsToLive: Int) {
        Timber.i("onLosing,network=$network,maxMsToLive=$maxMsToLive")
    }

    /**
     * 当网络断开或因其他原因不再满足此请求或回调时调用。
     * 当某个网络被系统认为 不可用/丢失 时回调，比如：Wi-Fi 被关闭/断开、移动网络关闭/信号丢失、有线网被拔掉，注意：并不代表设备没有网络，只是这个 Network 失效了。
     * 如果回调函数是用requestNetwork（）或registerDefaultNetworkCallback（）注册的，那么它只会在网络丢失且没有其他网络满足请求的条件时，对onAvailable（）返回的最后一个网络调用。
     * 如果回调函数注册了registerNetworkCallback()，那么每个不再满足回调条件的网络都会被调用。
     * 不要在此回调中调用 getNetworkCapabilities(Network) 或 getLinkProperties(Network) 或其他同步ConnectivityManager方法，
     * 因为这容易出现竞争条件；在回调函数中调用这些方法可能会返回过时的对象，甚至null对象。
     * @param network 丢失的Network。该值不能为 null 。
     */
    override fun onLost(network: Network) {
        printNetwork("onLost", network)
        // 为了预防短时间多次调用的情况，这里延迟2秒再调用onLost
        handler.removeCallbacksAndMessages(null)
        handler.postDelayed({ noNetwork() }, 2000)
    }


    /**
     * 在网络预留时调用。保留包括 NetworkCapabilities ，唯一地描述了所保留的网络。
     * 调用者将此信息传递给设备上或设备下的硬件或软件组件，以指示它们创建与此预留匹配的网络。最多被调用一次，并且保证在任何其他回调之前被调用，除非预留不可用。
     * 一旦预约完成，保留的 NetworkCapabilities 将不会更新，直到预约的网络连接或 onUnavailable 被调用。
     * @param networkCapabilities 预留的 NetworkCapabilities 。该值不能为 null 。
     */
    override fun onReserved(networkCapabilities: NetworkCapabilities) {
        Timber.i("onReserved,networkCapabilities=$networkCapabilities")
    }

    private fun printNetwork(tag: String, network: Network) {
        connectivityManager.getNetworkCapabilities(network)?.let { capabilities ->
            val isWifi = capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
            val isCellular = capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
            val isVPN = capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)
            val isEthernet = capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)
            val isInternet = capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
            var isValidatedStr = ""
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                // Android 6.0或以上
                val isValidated = capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
                isValidatedStr = ",isValidated=$isValidated"
            }

            // NET_CAPABILITY_INTERNET，只是网络自我声明“我能上网”,不保证真的能连通外部互联网，比如Wi-Fi路由器虽然能分配 IP，但没有外网（比如路由器没插网线），它还是会有 INTERNET 能力。
            // NET_CAPABILITY_VALIDATED，系统已经 验证过 这个网络的互联网连接 真的可用。Android 会后台尝试访问 Google 的连通性检测服务器（connectivitycheck.gstatic.com 等），如果能成功，就标记为 VALIDATED。
            // 如果设备在中国大陆且无法访问 Google 探测服务器，常常会导致网络有 INTERNET 但没有 VALIDATED。

            var ip = ""
            connectivityManager.getLinkProperties(network)?.let { linkProperties ->
                val interfaceName = linkProperties.interfaceName
                val linkAddresses = linkProperties.linkAddresses
                val ipv4 = linkAddresses.firstOrNull { it.address is Inet4Address && it.address.isLinkLocalAddress.not() }?.address?.hostAddress
                val ipv6 = linkAddresses.firstOrNull { it.address is Inet6Address && it.address.isLinkLocalAddress.not() }?.address?.hostAddress
                ip = "$,ip=${NetInterface(interfaceName, ipv4, ipv6)}"
            }

            Timber.i("${tag}：isWifi=$isWifi,isCellular=$isCellular,isVPN=$isVPN,isEthernet=$isEthernet,isInternet=$isInternet$isValidatedStr$ip")
        }
    }

}